import 'dart:convert';
import 'dart:math';
import 'package:logger/logger.dart';

/// Outputs simple log messages:
/// ```
/// [E] Log message  ERROR: Error info
/// ```
class CustomLogPrinter extends LogPrinter {
  static final _deviceStackTraceRegex = RegExp(r'#[0-9]+\s+(.+) \((\S+)\)');
  static final _webStackTraceRegex = RegExp(r'^((packages|dart-sdk)/\S+/)');
  static final _browserStackTraceRegex = RegExp(r'^(?:package:)?(dart:\S+|\S+)');

  static final levelPrefixes = {
    Level.trace: '[T]',
    Level.debug: '[D]',
    Level.info: '[I]',
    Level.warning: '[W]',
    Level.error: '[E]',
    Level.fatal: '[FATAL]',
  };

  static final levelColors = {
    Level.trace: AnsiColor.fg(AnsiColor.grey(0.5)),
    Level.debug: const AnsiColor.none(),
    Level.info: const AnsiColor.fg(12),
    Level.warning: const AnsiColor.fg(208),
    Level.error: const AnsiColor.fg(196),
    Level.fatal: const AnsiColor.fg(199),
  };

  final bool colors;
  final int? errorMethodCount;

  final int? methodCount;
  final int stackTraceBeginIndex;
  final DateTimeFormatter dateTimeFormat;

  //是否使用多行log打印
  final bool isMultiLine;

  CustomLogPrinter({
    this.colors = true,
    this.errorMethodCount = 8,
    this.methodCount = 0,
    this.stackTraceBeginIndex = 2,
    this.isMultiLine = false,
    DateTimeFormatter? dateTimeFormat,
  }) : dateTimeFormat = dateTimeFormat ?? DateTimeFormat.dateAndTime;

  @override
  List<String> log(LogEvent event) {
    String? stackTraceStr;
    if (event.error != null) {
      if ((errorMethodCount == null || errorMethodCount! > 0)) {
        stackTraceStr = formatStackTrace(
          event.stackTrace ?? StackTrace.current,
          errorMethodCount,
        );
      }
    } else if (methodCount == null || methodCount! > 0) {
      stackTraceStr = formatStackTrace(
        event.stackTrace ?? StackTrace.current,
        methodCount,
      );
    }
    var messageStr = stringifyMessage(event.message);
    var errorStr = event.error?.toString();
    var timeStr = getTime(event.time);
    return _formatAndPrint(
      event.level,
      messageStr,
      timeStr,
      errorStr,
      stackTraceStr,
    );
  }

  String getTime(DateTime time) {
    return dateTimeFormat(time);
  }

  bool _discardDeviceStacktraceLine(String line) {
    var match = _deviceStackTraceRegex.matchAsPrefix(line);
    if (match == null) {
      return false;
    }
    final segment = match.group(2)!;
    if (segment.startsWith('package:logger')) {
      return true;
    }
    return false;
  }

  bool _discardWebStacktraceLine(String line) {
    var match = _webStackTraceRegex.matchAsPrefix(line);
    if (match == null) {
      return false;
    }
    final segment = match.group(1)!;
    if (segment.startsWith('packages/logger') || segment.startsWith('dart-sdk/lib')) {
      return true;
    }
    return false;
  }

  bool _discardBrowserStacktraceLine(String line) {
    var match = _browserStackTraceRegex.matchAsPrefix(line);
    if (match == null) {
      return false;
    }
    final segment = match.group(1)!;
    if (segment.startsWith('package:logger') || segment.startsWith('dart:')) {
      return true;
    }
    return false;
  }

  String? formatStackTrace(StackTrace? stackTrace, int? methodCount) {
    List<String> lines = stackTrace
        .toString()
        .split('\n')
        .where(
          (line) => !_discardDeviceStacktraceLine(line) && !_discardWebStacktraceLine(line) && !_discardBrowserStacktraceLine(line) && line.isNotEmpty,
    )
        .toList();
    List<String> formatted = [];

    int stackTraceLength = (methodCount != null ? min(lines.length, methodCount) : lines.length);
    for (int count = 0; count < stackTraceLength; count++) {
      var line = lines[count];
      if (count < stackTraceBeginIndex) {
        continue;
      }
      formatted.add('#$count   ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}');
    }

    if (formatted.isEmpty) {
      return null;
    } else {
      return formatted.join('\n');
    }
  }

  // Handles any object that is causing JsonEncoder() problems
  Object toEncodableFallback(dynamic object) {
    return object.toString();
  }

  String stringifyMessage(dynamic message) {
    final finalMessage = message is Function ? message() : message;
    if (finalMessage is Map || finalMessage is Iterable) {
      var encoder = JsonEncoder.withIndent('  ', toEncodableFallback);
      return encoder.convert(finalMessage);
    } else {
      return finalMessage.toString();
    }
  }

  AnsiColor _getLevelColor(Level level) {
    AnsiColor? color;
    if (colors) {
      color = levelColors[level];
    }
    return color ?? const AnsiColor.none();
  }

  List<String> _formatAndPrint(
      Level level,
      String message,
      String time,
      String? error,
      String? stacktrace,
      ) {
    List<String> buffer = [];
    String resultMessage = "";
    String start = '$time ${levelPrefixes[level]!}';
    String startEmpty = ' ' * start.length;
    var color = _getLevelColor(level);
    void addLine(String line) {
      if (isMultiLine) {
        if (buffer.isEmpty) {
          buffer.add("$start $line");
        } else {
          buffer.add("$startEmpty $line");
        }
      } else {
        if (resultMessage.isEmpty) {
          resultMessage += "$start $line";
        } else {
          resultMessage += "\n$startEmpty $line";
        }
      }
    }

    if (error != null) {
      for (var line in error.split('\n')) {
        addLine(color(line));
      }
    }

    if (stacktrace != null) {
      for (var line in stacktrace.split('\n')) {
        addLine(color(line));
      }
    }
    for (var line in message.split('\n')) {
      addLine(color(line));
    }
    if (isMultiLine) {
      return buffer;
    } else {
      return [resultMessage];
    }
  }
}
